package com.ming.study.service.impl;

import com.ming.study.exceptionHandler.MingException;
import com.ming.study.pojo.Course;
import com.ming.study.pojo.UserInfo;
import com.ming.study.pojo.vo.*;
import com.ming.study.service.JwService;
import com.ming.study.utils.CookiesUtils;
import com.ming.study.utils.JwPropertiesUtil;
import com.ming.study.utils.ResultCodeImpl;
import com.mysql.cj.util.StringUtils;
import lombok.Data;
import org.jsoup.Connection;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.beans.Encoder;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;

@Service
public class JwServiceImpl implements JwService {
    @Autowired
    CookiesUtils cookiesUtils;

    /**
     * 登录获取cookie
     * @return
     */
    @Override
    public boolean login(UserLoginVo userLoginVo) {
        Map<String, String> postData = new HashMap<>();
        postData.put("userAccount", userLoginVo.getUserAccount());
        postData.put("userPassword", userLoginVo.getUserPassword());
        String encoded = "";
        for(int i = 0; i < 2; i++) { // 尝试多次获取验证码
            encoded = this.getEncoded(userLoginVo);
            if(!encoded.equals("no")) {
                break;
            }
            encoded = "";
        }
        postData.put("encoded", encoded);
        Connection connect = Jsoup.connect(JwPropertiesUtil.LOGIN_URL);
        try{
            // 进行登录
            Connection.Response res = connect
                    .followRedirects(false) // 必须关闭重定向
                    .cookies(cookiesUtils.getCookies(userLoginVo.getUserAccount()))
                    .data(postData)
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .method(Connection.Method.POST)
                    .execute();
            cookiesUtils.saveCookies(userLoginVo.getUserAccount(), res.cookies());
            // 登录需要重定向
            String location = res.header("Location");
            Connection loginConnect = Jsoup.connect(location); // http://kdjw.hnust.edu.cn/jsxsd/xk/LoginToXk?method=jwxt&ticqzket=b4d2df6XXX
            Connection.Response loginRes = loginConnect
                    .cookies(cookiesUtils.getCookies(userLoginVo.getUserAccount()))
                    .execute();
            cookiesUtils.saveCookies(userLoginVo.getUserAccount(), loginRes.cookies());
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 获取并加工验证码
     * @return
     */
    @Override
    public String getEncoded(UserLoginVo userLoginVo) {
        try{
            Connection.Response res = Jsoup.connect(JwPropertiesUtil.ENCODED_URL)
                                        .timeout(JwPropertiesUtil.NETWORK_TIME)
                                        .method(Connection.Method.POST)
                                        .execute();
            cookiesUtils.saveCookies(userLoginVo.getUserAccount(), res.cookies());
            Document document = res.parse();
            String dataStr = document.body().text(); // 未处理的验证码
            if(dataStr.equals("no")){
                return "no";
            }
            // 来源教务网，进行账号密码与验证码加密
            String scode =  dataStr.split("#")[0];
            String sxh = dataStr.split("#")[1];
            String code = userLoginVo.getUserAccount() + "%%%" + userLoginVo.getUserPassword();
            String encoded = "";
            for(int i = 0; i < code.length(); i++) {
                if (i < 20) {
                    encoded = encoded + code.substring(i, i +1) + scode.substring( 0, Integer.parseInt(sxh.substring(i, i +1)) );
                    scode = scode.substring( Integer.parseInt(sxh.substring(i, i +1)), scode.length() );
                } else {
                    encoded = encoded + code.substring(i, code.length());
                    i = code.length();
                }
            }
            return encoded;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取首页
     * @return
     */
    @Override
    public String getIndex(String userAccount) {
        try{
            Connection.Response res = Jsoup.connect(JwPropertiesUtil.INDEX_URL)
                    .cookies(cookiesUtils.getCookies(userAccount))
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .execute();
            Document document = res.parse();
            return document.html();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取课程
     * @param userAccount
     * @param date 2020-11-29
     * @return
     */
    @Override
    public List<List<Course>> getCourseFromWeek(String userAccount, String date) {
        List<List<Course>> allCourseList = new ArrayList<>();
        try{
            Connection.Response res = Jsoup.connect(JwPropertiesUtil.SEARCH_COURSE_URL + date)
                    .cookies(cookiesUtils.getCookies(userAccount))
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .execute();
            Document document = res.parse();
            Elements tds = document.getElementsByTag("td");
            System.out.println(document);
            System.out.println(tds.size());
            if(tds.size() != 42) {
                throw new MingException(ResultCodeImpl.ERROR_JW_SLOW);
            }
            final int MAX_H = 5,  MAX_L = 8; // 最大行，5行，8列，第一行为周次
            for(int h = 0; h < MAX_H; h++) { // 获取并保存行
                List<Course> oneRowCourseList  = new ArrayList<>();
                for(int l = 1; l < MAX_L; l++) { // 获取并保存列
                    Element td = tds.get(h * MAX_L + l);
                    String elementAttrTitle;
                    Elements courseDataTemp = td.getElementsByTag("p");
                    Course course = new Course();
                    if(!courseDataTemp.isEmpty()) {
                        Element courseData = courseDataTemp.get(0);
                        elementAttrTitle = courseData.attr("title");
                        // 课程学分：2<br/>课程属性：公选<br/>课程名称：书法鉴赏(公选)<br/>上课时间：第13周 星期三 [09-10]节<br/>上课地点：十教东附楼203
                        String[] allArg = elementAttrTitle.split("<br/>");
                        course.setCredit( allArg[0].substring(5) ); // 学分 课程学分：
                        course.setProperties( allArg[1].substring(5) ); // 属性 课程属性：
                        course.setCourseName( allArg[2].substring(5) ); // 名称
                        course.setTime( allArg[3].substring(5) );// 时间
                        course.setSite( allArg[4].substring(5) );// 地点
                    }
                    oneRowCourseList.add(course);
                }
                allCourseList.add(oneRowCourseList);
            }
        } catch (MingException e) {
            throw e;
        } catch (Exception e) {
            throw new MingException(ResultCodeImpl.NOT_LOGIN);
        }
        System.out.println(allCourseList);
        return allCourseList;
    }

    /**
     * 获取周次
     * @param userAccount
     * @return
     */
    public WeekOptionsVo getWeekOptions(String userAccount) {
        WeekOptionsVo weekOptionsVo = new WeekOptionsVo();
        try{
            Connection.Response res = Jsoup.connect(JwPropertiesUtil.WEEK_URL)
                    .cookies(cookiesUtils.getCookies(userAccount))
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .execute();
            Document document = res.parse();
            Element weekElement = document.getElementById("week");
            Elements optionElements = weekElement.getElementsByTag("option");
            // <option value="2020-09-07">第一周</option>
            List<WeekOptionVo> weekOptionVoList = new LinkedList<>();
            for (Element optionElement : optionElements) {
                if(optionElement.val().equals("")) {
                    continue;
                }
                WeekOptionVo weekOptionVo = new WeekOptionVo();
                weekOptionVo.setWeekName(optionElement.html());
                weekOptionVo.setWeekDate(optionElement.val());
                // 默认周次
                if(optionElement.hasAttr("selected")) {
                    weekOptionsVo.setDefaultOption(weekOptionVo);
                }
                weekOptionVoList.add(weekOptionVo);
            }
            weekOptionsVo.setWeekOptionList(weekOptionVoList);
        } catch (Exception e) {
            throw new MingException(ResultCodeImpl.ERROR_JW_SLOW);
        }
        return weekOptionsVo;
    }

    @Override
    public UserInfo getUserInfo(String userAccount) {
        try{
            Connection.Response res = Jsoup.connect(JwPropertiesUtil.USER_INFO_URL)
                    .cookies(cookiesUtils.getCookies(userAccount))
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .execute();
            Document document = res.parse();
            UserInfo userInfo = new UserInfo();
            userInfo.setName( document.getElementById("xm").val() );
            userInfo.setUserAccount( document.getElementById("xh").val() );
            userInfo.setDepartment( document.getElementById("yx").val() );
            userInfo.setGrade( document.getElementById("rxnf").val() );
            userInfo.setMajor( document.getElementById("zy").val() );
            userInfo.setUserClass( document.getElementById("bj").val() );
            return userInfo;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取所有课程成绩
     * @param userAccount
     * @param sortType 0正序，1反序
     * @return
     */
    @Override
    public CourseScoresVo getCourseScores(String userAccount, int sortType) {
        try {
            Connection.Response res = Jsoup.connect(JwPropertiesUtil.COURSE_SCORE_URL)
                    .cookies(cookiesUtils.getCookies(userAccount))
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .execute();
            Document document = res.parse();
            if (document.isBlock()) {
                throw new MingException(ResultCodeImpl.NOT_LOGIN);
            }
            CourseScoresVo courseScoresVo = new CourseScoresVo();
            // 汇总
            // 查询条件：全部 所修门数:46 所修总学分:114 平均学分绩点:3.44 平均成绩:86.46
            String gatherData = document.getElementsByTag("div").first().ownText();
            if( !StringUtils.isNullOrEmpty(gatherData) ) {
                String[] strArr = gatherData.split(" ");
                String[] targetNum = new String[4]; // 门数 总学分 平均学分绩点 平均成绩
                for (int i = 1; i < strArr.length; i++) {
                    String[] split = strArr[i].split(":");
                    targetNum[i - 1] = split[1];
                }
                courseScoresVo.setCourseCount(Integer.parseInt(targetNum[0]));
                courseScoresVo.setCreditCount(Float.parseFloat(targetNum[1]));
                courseScoresVo.setGPA(Float.parseFloat(targetNum[2]));
                courseScoresVo.setAverageScore(Float.parseFloat(targetNum[3]));
            }
            // 每门课程
            Element tableElement = document.getElementById("dataList");
            Elements trElements = tableElement.getElementsByTag("tr");
            trElements.remove(0); // 移除标题
            List<CourseScoreVo> courseScoreVoList = new LinkedList<>();
            for (Element trElement : trElements) { // 每行
                Elements gridElements = trElement.getElementsByTag("td"); // 一行中的各格
                CourseScoreVo courseScoreVo = new CourseScoreVo();
                courseScoreVo.setSerial(Integer.parseInt(gridElements.get(0).text()));
                courseScoreVo.setCourseTime(gridElements.get(1).text());
                courseScoreVo.setSerialNumber(gridElements.get(2).text());
                courseScoreVo.setCourseName(gridElements.get(3).text());
                courseScoreVo.setScore(gridElements.get(4).text());
                courseScoreVo.setScoreMark(gridElements.get(5).text());
                courseScoreVo.setCredit(Float.parseFloat(gridElements.get(6).text()));
                courseScoreVo.setTotalPeriod(Integer.parseInt(gridElements.get(7).text()));
                courseScoreVo.setGPA(Float.parseFloat(gridElements.get(8).text()));
                courseScoreVo.setRestartTerm(gridElements.get(9).text());
                courseScoreVo.setEvaluationMode(gridElements.get(10).text());
                courseScoreVo.setExamType(gridElements.get(11).text());
                courseScoreVo.setCourseProperties(gridElements.get(12).text());
                courseScoreVo.setCourseType(gridElements.get(13).text());
                courseScoreVoList.add(courseScoreVo);
            }
            courseScoresVo.setCourseScoreVoList(courseScoreVoList);
            return courseScoresVo;
        } catch (MingException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new MingException(ResultCodeImpl.ERROR_UN_KNOW);
        }
    }

    /**
     * 获取默认学期
     * @param userAccount
     * @return
     */
    @Override
    public String getDefaultTerm(String userAccount) {
        try {
            Connection.Response res = Jsoup.connect(JwPropertiesUtil.DEFAULT_TERM_URL)
                    .cookies(cookiesUtils.getCookies(userAccount))
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .execute();
            Document document = res.parse();
            Elements selectedElements = document.getElementsByAttribute("selected");
            if (selectedElements.isEmpty()) {
                throw new MingException(ResultCodeImpl.NOT_LOGIN);
            }
            Element defaultTermElement = selectedElements.first();
            String defaultTerm = defaultTermElement.val();
            return defaultTerm;
        } catch (MingException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new MingException(ResultCodeImpl.ERROR_UN_KNOW);
        }
    }

    @Override
    public List<ExamVo> getAllExamSchedule(String userAccount) {
        List<ExamVo> examVoList = new LinkedList<>();
        Map<String, String> dataMap = new HashMap<>();
        dataMap.put("xnxqid", this.getDefaultTerm(userAccount));
        for(int xqlb = 1; xqlb <= 3; xqlb++) { // 期初、期中、期末
            dataMap.put("xqlb", String.valueOf(xqlb));
            try {
                Connection.Response res = Jsoup.connect(JwPropertiesUtil.EXAM_SCHEDULE_URL)
                        .cookies(cookiesUtils.getCookies(userAccount))
                        .data(dataMap)
                        .method(Connection.Method.POST)
                        .timeout(JwPropertiesUtil.NETWORK_TIME)
                        .execute();
                Document document = res.parse();
                Elements trElements = document.getElementsByTag("tr");
                if (trElements.isEmpty()) {
                    throw new MingException(ResultCodeImpl.NOT_LOGIN);
                }
                trElements.remove(0);
                for (Element trElement : trElements) { // 每一个考试
                    Elements tdElements = trElement.getElementsByTag("td");
                    if(tdElements.get(0).attr("colspan").equals("12")) { // 没有数据
                        break;
                    }
                    ExamVo examVo = new ExamVo();
                    examVo.setExamName( tdElements.get(4).text() );
                    examVo.setCourseTeacherName( tdElements.get(5).text() );
                    String[] examDateAndTime = tdElements.get(6).text().split(" "); // 2021-01-18 08:00~09:40
                    examVo.setExamDate( examDateAndTime[0] );
                    examVo.setExamTime( examDateAndTime[1] );
                    examVo.setSite( tdElements.get(7).text() );
                    examVoList.add(examVo);
                }
            } catch (MingException e) {
                throw e;
            } catch (Exception e) {
                e.printStackTrace();
                throw new MingException(ResultCodeImpl.ERROR_UN_KNOW);
            }
        }
        return examVoList;
    }

    /**
     * 获取考级证书成绩
     * @param userAccount
     * @return
     */
    @Override
    public List<CertificateScoreVo> getAllCertificateScore(String userAccount) {
        List<CertificateScoreVo> certificateScoreVoList = new LinkedList<>();
        try{
            Connection.Response resp = Jsoup.connect(JwPropertiesUtil.CERTIFICATE_SCORE_URL)
                    .cookies(cookiesUtils.getCookies(userAccount))
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .execute();
            Document document = resp.parse();
            Elements trElements = document.getElementsByTag("tr");
            if (trElements.isEmpty()) {
                throw new MingException(ResultCodeImpl.NOT_LOGIN);
            }
            trElements.remove(0); // 删除头部标题
            trElements.remove(0);
            trElements.remove(trElements.last());
            /*
                序号	考级课程(等级)	分数类成绩	等级类成绩	考级时间
                笔试	机试	总成绩	笔试	机试	总成绩
                1	全国大学英语四六级考试（CET4）	0	0	388				2019-06-15
            */
            for (Element trElement : trElements) { // 每一条等级考试成绩
                CertificateScoreVo certificateScoreVo = new CertificateScoreVo();
                Elements tdElements = trElement.getElementsByTag("td");
                certificateScoreVo.setExamName( tdElements.get(1).text() );
                if( !tdElements.get(4).text().equals("") ) { //分数类
                    certificateScoreVo.setWriteScore( tdElements.get(2).text() );
                    certificateScoreVo.setComputerScore( tdElements.get(3).text() );
                    certificateScoreVo.setTotalScore( tdElements.get(4).text() );
                } else if( !tdElements.get(7).text().equals("") ) {
                    certificateScoreVo.setWriteScore( tdElements.get(5).text() );
                    certificateScoreVo.setComputerScore( tdElements.get(6).text() );
                    certificateScoreVo.setTotalScore( tdElements.get(7).text() );
                }
                certificateScoreVo.setExamTime( tdElements.get(8).text() );
                certificateScoreVoList.add( certificateScoreVo );
            }
        } catch (MingException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new MingException(ResultCodeImpl.ERROR_UN_KNOW);
        }
        return certificateScoreVoList;
    }

    /**
     * 获取课程表底部备注
     * @param userAccount
     * @return
     */
    @Override
    public List<CourseScheduleRemark> getCourseScheduleRemark(String userAccount) {
        try{
            Connection.Response resp = Jsoup.connect(JwPropertiesUtil.COURSE_SCHEDULE_URL)
                    .cookies(cookiesUtils.getCookies(userAccount))
                    .timeout(JwPropertiesUtil.NETWORK_TIME)
                    .execute();
            Document document = resp.parse();
            Elements tableElements = document.getElementsByTag("table");
            if (tableElements.isEmpty()) {
                throw new MingException(ResultCodeImpl.NOT_LOGIN);
            }
            Element courseElement = tableElements.get(0);
            Element remarkElement = courseElement.getElementsByTag("td").last();
            String[] courseArr = remarkElement.text().split(";");
            List<CourseScheduleRemark> courseScheduleRemarkList = new LinkedList<>();
            for (String course : courseArr) {
                CourseScheduleRemark courseScheduleRemark = new CourseScheduleRemark();
                try{
                    String[] courseData = course.split(" ");
                    courseScheduleRemark.setCourseName( courseData[0] );
                    courseScheduleRemark.setTeacher( courseData[1]);
                    courseScheduleRemark.setCourseTime( courseData[2] );
                    courseScheduleRemarkList.add(courseScheduleRemark);
                } catch (ArrayIndexOutOfBoundsException e) {
                    throw new MingException(ResultCodeImpl.ERROR_ANALYSIS);
                }
            }
            return courseScheduleRemarkList;
        } catch (MingException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new MingException(ResultCodeImpl.ERROR_UN_KNOW);
        }
    }
}
